﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace Utilities.Net
{
    public class TcpStreamEventArgs : EventArgs
    {
        public TcpStream Stream { get; protected set; }

        public TcpStreamEventArgs(TcpStream stream)
        {
            Stream = stream;
        }
    }
    public class ServerDiscoveryEventArgs : EventArgs
    {
        public IPEndPoint IpEndPoint { get; protected set; }
        public string Data { get; protected set; }

        public ServerDiscoveryEventArgs(IPEndPoint ipEndPoint, string data)
        {
            IpEndPoint = ipEndPoint;
            Data = data;
        }
    }

    public class TcpServer
    {
        const string discoveryStr = "[DISCOVERY]";
        const string serverStr = "[SERVER]";

        protected int port;
        protected TcpListener tcpListener;
        protected UdpClient udpListener;

        protected static UdpClient udpDiscoveryClient = null;
        protected static UdpClient udpDiscoveryListener = null;
        protected static bool discovering = false;

        public List<TcpStream> Clients { get; protected set; }
        public string DiscoveryData { get; set; }

        protected bool discoveryEnabled = true;
        public bool DiscoveryEnabled
        {
            get
            {
                return discoveryEnabled;
            }
            set
            {
                discoveryEnabled = value;
                if (value)
                    udpListener.BeginReceive(OnSelfDiscovery, port);
                else
                    udpListener.Close();
            }
        }

        public bool Listening { get; protected set; }

        public TcpServer(int port)
        {
            this.port = port;
            tcpListener = new TcpListener(IPAddress.Any, port);

            Clients = new List<TcpStream>();

            udpListener = new UdpClient(port);
            udpListener.BeginReceive(OnSelfDiscovery, port);
        }

        public static void StartDiscovery(int port)
        {
            StopDiscovery();

            udpDiscoveryListener = new UdpClient(port);
            udpDiscoveryListener.BeginReceive(OnServerDiscovery, port);

            byte[] data = Encoding.Default.GetBytes(discoveryStr);
            udpDiscoveryClient = new UdpClient();
            udpDiscoveryClient.Send(data, data.Length, new IPEndPoint(IPAddress.Broadcast, port));

            discovering = true;
        }
        public static void StopDiscovery()
        {
            if (udpDiscoveryClient != null)
                udpDiscoveryClient.Close();
            if (udpDiscoveryListener != null)
                udpDiscoveryListener.Close();

            udpDiscoveryClient = udpDiscoveryListener = null;

            discovering = false;
        }
        protected virtual void OnSelfDiscovery(IAsyncResult asyncResult)
        {
            if (!discoveryEnabled)
                return;

            int port = (int)asyncResult.AsyncState;
            IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Broadcast, port);
            string discoveryData = Encoding.Default.GetString(udpListener.EndReceive(asyncResult, ref ipEndPoint));

            if (discoveryData == discoveryStr)
            {
                byte[] data = Encoding.Default.GetBytes(serverStr + DiscoveryData ?? "");
                ipEndPoint.Port = port;
                udpListener.Send(data, data.Length, ipEndPoint);
            }

            if (discoveryEnabled)
                udpListener.BeginReceive(OnSelfDiscovery, port);
        }
        protected static void OnServerDiscovery(IAsyncResult asyncResult)
        {
            string serverData = "";

            if (udpDiscoveryListener == null)
                return;

            int port = (int)asyncResult.AsyncState;
            IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, port);
            try
            {
                serverData = Encoding.Default.GetString(udpDiscoveryListener.EndReceive(asyncResult, ref ipEndPoint));
            }
            catch
            {
                return;
            }

            if (ServerDiscovery != null && discovering && serverData.StartsWith(serverStr))
                ServerDiscovery(null, new ServerDiscoveryEventArgs(ipEndPoint, serverData.Substring(serverStr.Length)));
            if (discovering)
                udpDiscoveryListener.BeginReceive(OnServerDiscovery, port);
        }

        public virtual void Listen()
        {
            Listening = true;
            tcpListener.Start();
            tcpListener.BeginAcceptTcpClient(OnClientConnection, null);
        }
        public virtual void Stop()
        {
            Listening = false;
            tcpListener.Stop();
        }
        protected virtual void OnClientConnection(IAsyncResult asyncResult)
        {
            if (!Listening)
                return;

            TcpClient tcpClient = tcpListener.EndAcceptTcpClient(asyncResult);
            TcpStream client = new TcpStream(tcpClient);
            client.Disconnect += s => Clients.Remove(s);

            Clients.Add(client);

            if (ClientConnection != null)
                ClientConnection(this, new TcpStreamEventArgs(client));

            if (Listening)
                tcpListener.BeginAcceptTcpClient(OnClientConnection, null);
        }

        public event EventHandler<TcpStreamEventArgs> ClientConnection;
        public static event EventHandler<ServerDiscoveryEventArgs> ServerDiscovery;
    }
}
